#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <dirent.h>

#include <sys/types.h>
#include <arpa/inet.h>

#include "FtpServerFunctions.h"

/************************************************************************************************************************************************************************/
/*******************************************************      Strings for Acks and Errors      **************************************************************************/
/************************************************************************************************************************************************************************/
char* ftpErrorStr[] = {
                        "",
                        "Wrong input!!",
                        "ERROR: connection not established",
                        "ERROR: client not found. Start over the connection",
                        "Wrong user. Try again!!" ,
                        "Wrong password - try again"     ,
                        "Wrong password on third time\nPlease enter user name:",
                        "ERROR: file not exist",
                        "ERROR: error occured while reading the file"   ,
                        "ERROR: error occured while transfering file"   ,
                        "ERROR: error occured during cd"                ,
                        "Invalid directory "                            ,
                        "ERROR: ls failure"                             ,
                        "ERROR: file not created"
};


char* ftpAckStr[] = {
                            "Welcome - Please enter user and password for logging"  ,
                            "User correct. Now enter password:"                     ,
                            "Logged in"                                             ,
                            "Dir changed"                                           ,
                            "File Transfer..."                                      ,
                            "logged out"
};

/************************************************************************************************************************************************************************/
/**********************************************************       Inner Functions Imp      ******************************************************************************/
/************************************************************************************************************************************************************************/
int SendStream(GenServer* _server, GSKey _clientKey, char* _buffer, size_t _dataSize, int _errNum)
{
    char sendBuf[BUFFER_SIZE];

    assert(_server);
    assert(_buffer);

    AddFtpCmdHeader(sendBuf, _dataSize, _errNum);
    memcpy(sendBuf + sizeof(FtpCmdHeader), _buffer, _dataSize);
    GenServerWrite(_server, _clientKey, sendBuf, sizeof(FtpCmdHeader) + _dataSize);

    return 0;
}

int SendError(GenServer* _server, GSKey _clientKey, int _errNum)
{
    char    sendBuf[BUFFER_SIZE];
    size_t  strSize;

    assert(_server);

    strSize = strlen(ftpErrorStr[_errNum]); /*  the size of the error string    */

    AddFtpCmdHeader(sendBuf, strSize, _errNum);
    memcpy(sendBuf + sizeof(FtpCmdHeader), ftpErrorStr[_errNum], strSize);
    GenServerWrite(_server, _clientKey, sendBuf, sizeof(FtpCmdHeader) + strSize);

    return 0;
}


int SendAck(GenServer* _server, GSKey _clientKey, int _ackNum)
{
    char    sendBuf[BUFFER_SIZE];
    size_t  strSize;

    assert(_server);

    strSize = strlen(ftpAckStr[_ackNum]); /*  the size of the ack string    */

    AddFtpCmdHeader(sendBuf, strSize, FTP_SUCCESS);
    memcpy(sendBuf + sizeof(FtpCmdHeader), ftpAckStr[_ackNum], strSize);
    GenServerWrite(_server, _clientKey, sendBuf, sizeof(FtpCmdHeader) + strSize);

    return 0;
}


void SendFileData(GenServer* _server, GSKey _clientKey, char* OUT _buffer, size_t _dataSize, size_t _pos, int _isLast, int _errNum)
{
    AddFtpCmdHeader(_buffer, _dataSize + sizeof(FtpDataHeader), _errNum);
    AddFtpDataHeader(_buffer + sizeof(FtpCmdHeader), _isLast, _pos);
    GenServerWrite(_server, _clientKey, _buffer, sizeof(FtpCmdHeader) + sizeof(FtpDataHeader) + _dataSize);
}


/*  Function for adding for given buffer header in sizeof(FtpCmdHeader) */
static void AddFtpCmdHeader(void* OUT _buffer, size_t _size, int _errNum)
{
    FtpCmdHeader* fHeader = 0;

    assert(_buffer);

    fHeader = (FtpCmdHeader*)_buffer;
    fHeader->m_innerHeader.u_realHead.m_dataSize = _size;
    fHeader->m_innerHeader.u_realHead.m_errNum   = _errNum;

    fHeader->m_innerHeader.u_innerHeaderAsInt = htonl(fHeader->m_innerHeader.u_innerHeaderAsInt);
}


void AddFtpDataHeader(void* OUT _buffer, int _isLast, size_t _pos)
{
    FtpDataHeader* fHeader = 0;

    assert(_buffer);

    fHeader = (FtpDataHeader*)_buffer;
    fHeader->m_innerDataHeader.u_realDataHead.m_isLast = _isLast;
    fHeader->m_innerDataHeader.u_realDataHead.m_pos    = _pos;
    fHeader->m_innerDataHeader.u_innerHeaderAsInt = htonl(fHeader->m_innerDataHeader.u_innerHeaderAsInt);   /*  using the union to switch format to network */
}


static int GSKeyLessFunc(void* _a, void* _b)
{
    return ((FtpClient*)_a)->m_clientKey < ((FtpClient*)_b)->m_clientKey;
}


int RecvFile(FtpServer* _server, FtpClient* _client, void* _buf, size_t _size)
{
    FtpDataHeader* fHeader = 0;

    assert(_client);
    assert(_server);
    assert(_buf);
/*
    if(status)                                            Check there isn't any error
    {
        _client->m_fileBuff = HandleEvent;
        return -1;
    }*/

    fHeader = (FtpDataHeader*)_buf;
    fHeader->m_innerDataHeader.u_innerHeaderAsInt = ntohl(fHeader->m_innerDataHeader.u_innerHeaderAsInt);   /*  using the union to switch format to host */

    _client->m_fileBuff.m_bytesRecv += _size - sizeof(FtpDataHeader);  /*  Counter for the bytes arrived   */
    fseek(_client->m_fileBuff.m_fp, fHeader->m_innerDataHeader.u_realDataHead.m_pos, SEEK_SET);  /*  Go to the right position in file    */
    if(fwrite((char*)_buf + sizeof(FtpDataHeader), 1,  _size - sizeof(FtpDataHeader), _client->m_fileBuff.m_fp) < _size - sizeof(FtpDataHeader))
    {
        perror("fwrite error");
        fclose(_client->m_fileBuff.m_fp);
        return -1;
    }

    if(fHeader->m_innerDataHeader.u_realDataHead.m_isLast == 1)
    {
        _client->m_fileBuff.m_fileLen = _size - sizeof(FtpDataHeader) + fHeader->m_innerDataHeader.u_realDataHead.m_pos;
    }
    if(_client->m_fileBuff.m_bytesRecv >= _client->m_fileBuff.m_fileLen && _client->m_fileBuff.m_fileLen)
    {
        _client->m_bufState = HandleEvent;
        fclose(_client->m_fileBuff.m_fp);
    }

    /*if(status)
    {
        *(recvBuf + msgLen) = '\0';
        printf("%s\n", recvBuf);
    }
    fclose(fp);*/


    return 0;
}


static int HandleEvent(FtpServer* _server, FtpClient* _client, void* _buf, size_t _size)
{
    FtpEvent    event           ;
    char        param[DIR_SIZE] ;
    TransCell   cell;
    FtpParam    ftpParam;

    if(FtpParser(_buf, _size, &event, param))
    {
        return -1;
    }

    ftpParam.m_fServer = _server;
    ftpParam.m_fClient = _client;
    ftpParam.m_buf     = param  ;
    ftpParam.m_size    = _size  ;

    cell = _server->m_eventTable[event][_client->m_eventState];
    if(!(*cell.m_ftpFunc)(&ftpParam))           /*  If success returned     */
    {
        _client->m_eventState = cell.m_nextState;
    }
    else                                        /*  On fail                 */
    {
        _client->m_eventState = cell.m_nextStateOnFail;
    }

    return 0;
}


int UsrLessFunc(void* _a, void* _b)
{
    if(strcmp( ((UsrAndPswd*)_a)->m_usr , ((UsrAndPswd*)_b)->m_usr ) < 0)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}


int FtpParser(void* _buffer, size_t _size, FtpEvent* OUT _event, char* OUT _param)
{
    int     i                                           ;
    char    buf[BUFFER_SIZE]                    = {0}   ;
    char*   cp                                  = 0     ;
    char*   ftpEvents[FTP_EVENT_NUM_OF_EVENTS]  = {"","user", "password", "cd", "ls", "bye", "get", "put"};

    memcpy(buf, _buffer, _size);    /*  Copy buffer         */
    cp = strchr(buf, ' ');          /*  Get ptr to space    */
    if(!cp)
    {
        *_param = '\0';             /*  No param            */
    }
    else
    {
        *cp = '\0';                 /*  Replace space with null terminated  */
        strcpy(_param, cp + 1);     /*  copy the param to output parameters */
    }

    if(strlen(buf) > SECURITY_CMD_SIZE)     /*  Check the cmd len   */
    {
        _event = FTP_EVENT_ERR;
        fprintf(stderr, "security overflow\n");
        return -1;
    }

    for(i = 1; i < FTP_EVENT_NUM_OF_EVENTS; ++i)
    {
        if(!strcmp(buf, ftpEvents[i]))              /*  Find the match command  */
        {
            *_event = i;
            break;
        }
    }
    if(i == FTP_EVENT_NUM_OF_EVENTS)                /*  Wrong cmd               */
    {
        *_event = FTP_EVENT_ERR;
        return 0;
    }

    return 0;
}


void FreeTreeItems(Tree* _tree)
{
    TreeIter it;

    assert(_tree);

    it = TreeBegin(_tree);
    while(!TreeIsEqual(it, TreeEnd(_tree)))
    {
        free((UsrAndPswd*)TreeGetData(it));
        it = TreeRemove(_tree, it);
    }
}


#ifdef GOGO
int InsertUsrAndPswd2Tree(Tree* _tree, char** _userArr, char** _pswdArr, size_t _numOfUsers)
{
    UsrAndPswd* usrAndPswd = 0;
    int i;

    FILE* fp;
    if(0 == (fp = fopen(_fName,"r")))
    {
        perror("error on openning file");
        return -1;
    }

    while(!feof(fp))
    {
        fscanf(fp, "%s", user[i]);
        fscanf(fp, "%s", pass[i]);
    }

    for(i = 0;i < _numOfUsers; ++i)
    {
        usrAndPswd = (UsrAndPswd*)malloc(sizeof(UsrAndPswd));
        if(!usrAndPswd)
        {
            perror("error on allocating user and password");
            FreeTreeItems(_tree);
            return -1;
        }
        strcpy(usrAndPswd->m_usr, _userArr[i]);
        strcpy(usrAndPswd->m_pswd, _pswdArr[i]);
        if(TreeIsEqual(TreeInsert(_tree, usrAndPswd), TreeEnd(_tree)))          /*  Insert to tree  */
        {
            fprintf(stderr, "error on inserting to user and passwords tree\n");
            free(usrAndPswd);
            FreeTreeItems(_tree);
            return -1;
        }
    }

    return 0;
}
#endif

int InsertUsrAndPswd2Tree(Tree* _tree, char* _fName)
{
    UsrAndPswd* usrAndPswd = 0  ;
    FILE*       fp         = 0  ;
    int         n               ;

    assert(_fName);

    if(0 == (fp = fopen(_fName,"r")))
    {
        perror("error on openning file");
        return -1;
    }

    while(!feof(fp))
    {
        usrAndPswd = (UsrAndPswd*)malloc(sizeof(UsrAndPswd));
        if(!usrAndPswd)
        {
            perror("error on allocating user and password");
            FreeTreeItems(_tree);
            return -1;
        }
        n = fscanf(fp, "%s", usrAndPswd->m_usr);
        if(feof(fp))
        {
            free(usrAndPswd);
            break;
        }
        n = fscanf(fp, "%s", usrAndPswd->m_pswd);
        if(TreeIsEqual(TreeInsert(_tree, usrAndPswd), TreeEnd(_tree)))          /*  Insert to tree  */
        {
            fprintf(stderr, "error on inserting to user and passwords tree\n");
            free(usrAndPswd);
            FreeTreeItems(_tree);
            return -1;
        }
    }

    return 0;
}





static void RemoveFtpClient(FtpServer* _fServer, GSKey _key)
{
    TreeIter    it          ;
    FtpClient   client      ;
    FtpClient*  fClient = 0 ;

    assert(_fServer);

    client.m_clientKey = _key;                                          /*  Create fake client for finding the actual client by key */
    it = TreeFind(_fServer->m_ftpClientTree, &(client.m_clientKey));
    {
        if(TreeIsEqual(it,TreeEnd(_fServer->m_ftpClientTree)))
        {
            fprintf(stderr, "Client not exist on tree to be removed\n");
            return;
        }
    }
    fClient = (FtpClient*)TreeGetData(it);
    TreeRemove(_fServer->m_ftpClientTree, it);                          /*  Remove client from tree                                 */
    free(fClient->m_cwd);
    free(fClient);
}

/************************************************************************************************************************************************************************/
/**************************************************               FTP Commands Imp                            ***********************************************************/
/************************************************************************************************************************************************************************/
int FtpErr(void* _param)
{
    FtpParam* fParam = 0 ;

    assert(_param);

    fParam = (FtpParam*)_param;
    SendError(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_BAD_CMD);

    return 0;
}


int CheckUser(void* _param)
{
    FtpParam*   fParam      = 0  ;
    TreeIter    it               ;

    assert(_param);

    fParam = (FtpParam*)_param;
    strcpy(fParam->m_fClient->m_usrAndPswd.m_usr, fParam->m_buf);           /*  Making use of existing usrAndPswd struct for making fake struct for find    */
    it = TreeFind(fParam->m_fServer->m_usrAndPswdTree, &fParam->m_fClient->m_usrAndPswd.m_usr);
    if(TreeIsEqual(it, TreeEnd(fParam->m_fServer->m_usrAndPswdTree)))       /*  User not exist      */
    {
        SendError(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_WRONG_USR);
        return -1;
    }

    strcpy(fParam->m_fClient->m_usrAndPswd.m_pswd, ((UsrAndPswd*)TreeGetData(it))->m_pswd);
    SendAck(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ACK_USER);

    return 0;
}


int CheckPswd(void* _param)
{
    FtpParam*   fParam      = 0   ;

    assert(_param);

    fParam = (FtpParam*)_param;
    if(strcmp(fParam->m_fClient->m_usrAndPswd.m_pswd, fParam->m_buf))   /*  Check the correctnce of password    */
    {
        if(fParam->m_fClient->m_eventState != FS_WAIT_FOR_PSWD3)        /*  If not the third try                */
        {
            SendError(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_WRONG_PASS);
        }
        else                                                            /*  If it's the third try               */
        {
            SendError(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_WRONG_PASS3);
        }

        return -1;
    }

    SendAck(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ACK_LOGIN);

    return 0;
}


int FtpGet(void* _param)
{
    FtpParam*   fParam      = 0 ;
    char*       sendBuf     = 0 ;
    size_t      bytesRead       ;
    size_t      position    = 0 ;
    FILE*       fp          = 0 ;
    FtpServer*  fServer     = 0 ;
    int         isLast      = 0 ;
    char        path[DIR_SIZE]  ;

    assert(_param);

    fParam = (FtpParam*)_param;
    fServer = fParam->m_fServer;

    strcpy(path, fParam->m_fClient->m_cwd);
    strcat(path, "/");
    strcat(path, fParam->m_buf);
    if(0 == (fp = fopen(path, "r")))
    {
        perror("file not exist");
        SendError(fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_FILE_NOT_EXIST);

        return -1;
    }

    sendBuf = (char*)malloc(fServer->m_packetSize);     /*  Allocate buffer in packet size for send */
    if(!sendBuf)
    {
        fclose(fp);
        SendError(fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_FILE_TRANS);
        return -1;
    }

    while(!feof(fp))                        /*  Loop for reading from file          */
    {
        bytesRead = fread(sendBuf + sizeof(FtpCmdHeader) + sizeof(FtpDataHeader), 1, fServer->m_packetSize - sizeof(FtpCmdHeader) - sizeof(FtpDataHeader), fp);    /*  read size of packet - headers   */
        if(feof(fp))                        /*  If this the last packet             */
        {
            isLast = 1;
        }
        else if(ferror(fp))                 /*  If error occured send error message */
        {
            SendError(fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_READ_FILE_FAIL);
            break;
        }

        SendFileData(fServer->m_server, fParam->m_fClient->m_clientKey, sendBuf, bytesRead, position, isLast, FTP_SUCCESS);
        position += bytesRead;              /*  increment position in file          */
    }
    fclose(fp);
    free(sendBuf);

    return 0;
}


static int FtpCd(void* _param)
{
    FtpParam*   fParam    = 0   ;
    DIR*        dp        = 0   ;
    char*       newDir    = 0   ;
    char*       lastSlash = 0   ;
    FtpClient*  fClient   = 0   ;

    assert(_param);

    fParam = (FtpParam*)_param;

    fClient = fParam->m_fClient;
    printf("CWD is: %s   , param: %s\n", fClient->m_cwd, (char*)fParam->m_buf);
    if(!strcmp((char*)fParam->m_buf, ".."))         /*  If the "cd" parameter is ".."   */
    {
        /*if(!strcmp(fParam->m_fServer->m_baseDir, fClient->m_cwd))
        {
            SendError(fParam->m_fServer->m_server, fClient->m_clientKey, )
        }*/
        lastSlash = strrchr(fClient->m_cwd, '/');   /*  Find the last slash in CWD      */
        if(lastSlash)
        {
            *lastSlash = '\0';                      /*  Replace with terminating NULL   */
        }
        SendAck(fParam->m_fServer->m_server, fClient->m_clientKey, FTP_ACK_CD);
        return 0;
    }

    if(!strcmp((char*)fParam->m_buf, "\0"))
    {
        newDir = (char*)malloc(strlen(fParam->m_fServer->m_baseDir) + 1);
        if(!newDir)
        {
            SendError(fParam->m_fServer->m_server, fClient->m_clientKey, FTP_ERR_CD);
            perror("allocating new directory");
            return -1;
        }
        strcpy(newDir, fParam->m_fServer->m_baseDir);
        free(fClient->m_cwd);
        fClient->m_cwd = newDir;
        SendAck(fParam->m_fServer->m_server, fClient->m_clientKey, FTP_ACK_CD);
        return 0;
    }

    newDir = (char*)malloc(strlen(fClient->m_cwd) + strlen(fParam->m_buf) + 2);
    if(!newDir)
    {
        SendError(fParam->m_fServer->m_server, fClient->m_clientKey, FTP_ERR_CD);
        perror("allocating new directory");
        return -1;
    }

    strcpy(newDir, fClient->m_cwd);     /*  Copy cwd to new dir */
    strcat(newDir, "/");                /*  Add '/' after cwd   */
    strcat(newDir, fParam->m_buf);      /*  Add the param       */
    printf("new dir is: %s\n", newDir);

    dp = opendir(newDir);
    if(!dp)
    {
        perror("Couldn't open the directory");
        SendError(fParam->m_fServer->m_server, fClient->m_clientKey, FTP_ERR_DIR_NOT_EXIST);
        free(newDir);
        return -1;
    }

    SendAck(fParam->m_fServer->m_server, fClient->m_clientKey, FTP_ACK_CD);
    closedir(dp);
    free(fClient->m_cwd);
    fClient->m_cwd = newDir;

    return 0;
}


int FtpLs(void* _param)
{
    FtpParam*       fParam       = 0;
    DIR*            dp           = 0;
    struct dirent*  dirnetPtr    = 0;
    FtpServer*      fServer      = 0;
    FtpClient*      fClient      = 0;
    char*           sendBuf      = 0;
    size_t          bytesWritten = 0;
    size_t          bytesToWrite = 0;
    size_t          position     = 0;

    assert(_param);

    fParam = (FtpParam*)_param;
    fClient = fParam->m_fClient;
    fServer = fParam->m_fServer;

    dp = opendir(fClient->m_cwd);
    if(!dp)
    {
        perror ("Couldn't open the directory");
        SendError(fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_LS);
        return -1;
    }

    sendBuf = (char*)malloc(fServer->m_packetSize);     /*  Allocate buffer in packet size for send */
    if(!sendBuf)
    {
        perror("allocate buffer");
        SendError(fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_LS);
        closedir(dp);
        return -1;
    }

    dirnetPtr = readdir(dp);    /*  Read entry  */
    while(dirnetPtr)
    {
        bytesToWrite = strlen(dirnetPtr->d_name);
        if(fServer->m_packetSize - CMD_HEADER_SIZE - DATA_HEADER_SIZE < (bytesWritten + bytesToWrite + 1))  /*  If packet_size smaller than dir name + written bytes    */
        {
            SendFileData(fServer->m_server, fClient->m_clientKey, sendBuf, bytesWritten, position, 0, FTP_SUCCESS);
            position += bytesWritten;
            bytesWritten = 0;
        }

        memcpy(sendBuf + CMD_HEADER_SIZE + DATA_HEADER_SIZE + bytesWritten, dirnetPtr->d_name, bytesToWrite);
        *(sendBuf + CMD_HEADER_SIZE + DATA_HEADER_SIZE + bytesWritten + bytesToWrite) = '\n';
        bytesWritten += (bytesToWrite + 1);

        puts(dirnetPtr->d_name);
        dirnetPtr = readdir (dp);
    }
    if(bytesWritten)    /*  If there are more bytes to send */
    {
        SendFileData(fServer->m_server, fClient->m_clientKey, sendBuf, bytesWritten, position, 1, FTP_SUCCESS);
    }
    closedir(dp);
    free(sendBuf);

    return 0;
}


int FtpBye(void* _param)
{
    FtpParam* fParam    = 0             ;

    assert(_param);

    fParam = (FtpParam*)_param;
    SendAck(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ACK_BYE);
    GenServerDrop(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey);     /*  Drop the client from the GenericServer. Dissconnect callBack will be called  */

    return 0;
}


int FtpPut(void* _param)
{
    FtpParam*   fParam          = 0;
    FtpClient*  fClient         = 0;
    char        path[DIR_SIZE]     ;

    assert(_param);

    fParam = (FtpParam*)_param;
    fClient = fParam->m_fClient;

    strcpy(path, fParam->m_fClient->m_cwd);
    strcat(path, "/");
    strcat(path, fParam->m_buf);
    if(0 == (fClient->m_fileBuff.m_fp = fopen(path, "w+")))   /*  Open the file for write                         */
    {
        perror("fopen");
        SendError(fParam->m_fServer->m_server, fParam->m_fClient->m_clientKey, FTP_ERR_CREATE_FILE);
        return -1;
    }
    fClient->m_fileBuff.m_bytesRecv = 0;
    fClient->m_fileBuff.m_fileLen   = 0;
    fClient->m_bufState = RecvFile;                   /*  Change the buffer state to recieve file         */
    SendAck(fParam->m_fServer->m_server, fClient->m_clientKey, FTP_ACK_PUT);

    return 0;
}

/************************************************************************************************************************************************************************/
/**************************************************         CallBack Functions for GenericServer Imp         ************************************************************/
/************************************************************************************************************************************************************************/
size_t GetFtpHeader(void* _app, void* _header)
{
    ((FtpCmdHeader*)_header)->m_innerHeader.u_innerHeaderAsInt = ntohl(((FtpCmdHeader*)_header)->m_innerHeader.u_innerHeaderAsInt);

    return ((FtpCmdHeader*)_header)->m_innerHeader.u_realHead.m_dataSize;
}


void NewFtpClient(void* _app, GSKey _key)
{
    FtpClient* client = 0                                                       ;
    FtpServer* server = (FtpServer*)_app                                        ;

    assert(_key && _app);

    client = (FtpClient*)malloc(sizeof(FtpClient));         /*  Allocating new ftp client           */
    if(!client)
    {
        perror("error on allocating ftp client");
        goto clearClientAlloc;
    }

    client->m_clientKey = _key;
    client->m_cwd = get_current_dir_name();                 /*  Allocate and put the CWD                    */
    if(!client->m_cwd)
    {
        perror("get CWD");
        goto clearCwd;
    }

    client->m_eventState = FS_WAIT_FOR_USR;                 /*  Initial state is wait for user              */
    client->m_bufState = HandleEvent;                       /*  The buffer state is handle event (commands) */
    if(TreeIsEqual(TreeInsert(server->m_ftpClientTree, client), TreeEnd(server->m_ftpClientTree)))
    {
        fprintf(stderr, "error on inserting to tree\n");
        goto clearTreeInsert;
    }

    SendAck(server->m_server , _key, FTP_ACK_WELCOME);      /*  Sends "welcome" message             */

    return;


    clearTreeInsert:
        free(client->m_cwd);
    clearCwd:
        free(client);
    clearClientAlloc:
        SendError(server->m_server , _key, FTP_ERR_CONN_ERR);    /*  Sends error message             */
        GenServerDrop(server->m_server, _key);
}


void NewFtpMssg(void* _app, GSKey _key, void* _buffer, size_t _size)
{
    FtpClient*  client      = 0                                                     ;
    FtpClient   fakeClient                                                          ;
    FtpServer*  fServer     = 0                                                     ;
    TreeIter    it                                                                  ;

    assert(_key && _app && _buffer);

    fServer = (FtpServer*)_app;
    fakeClient.m_clientKey = _key;                                      /*  Create fake client for tree find                                                */
    it = TreeFind(fServer->m_ftpClientTree, &fakeClient);
    if(TreeIsEqual(it, TreeEnd(fServer->m_ftpClientTree)))              /*  client not found                                                                */
    {
        SendError(fServer->m_server, _key, FTP_ERR_CLIENT_NOT_FOUND);    /*  Send to client(by key) that error occured, need to start over                   */
        fprintf(stderr, "client not found\n");
    }

    client = (FtpClient*)TreeGetData(it);
    (*client->m_bufState )(fServer, client, _buffer, _size);            /*  Activate on arriving buffer the appropriate function (RecvFile \ HandleEvent)   */

}

void FtpDisconnect(void* _app, GSKey _key, size_t _nonRecvBytes, size_t _nonSendBytes)
{
    assert(_app);

    RemoveFtpClient((FtpServer*)_app, _key);
}


/************************************************************************************************************************************************************************/
/*******************************************************         InterFace Functions Imp          ***********************************************************************/
/************************************************************************************************************************************************************************/
FtpServer* FtpServerNew(int _port, char* _fileName, size_t _packetSize)
{
    FtpServer* fServer = 0;
    TransCell table[FTP_EVENT_NUM_OF_EVENTS][FS_NUM_OF_STATES] =
            /* STATE: |         ERROR           |               WAIT_FOR_USR                     |              WAIT_FOR_PSWD1                      |            WAIT_FOR_PSWD2                       |           WAIT_FOR_PSWD3                      |           LOGGED_IN                             |
        EVENT         |                         |                                                |                                                  |                                                 |                                               |                                                 |
        ------        |                         |                                                |                                                  |                                                 |                                               |                                                 |   */
    /* ERROR*/      { {{FtpErr, FS_ERR, FS_ERR}, {FtpErr, FS_WAIT_FOR_USR, FS_WAIT_FOR_USR}     ,{FtpErr, FS_WAIT_FOR_PSWD1, FS_WAIT_FOR_PSWD1} , {FtpErr, FS_WAIT_FOR_PSWD2, FS_WAIT_FOR_PSWD2}, {FtpErr, FS_WAIT_FOR_PSWD3, FS_WAIT_FOR_PSWD3} , {FtpErr, FS_LOGGED_IN, FS_LOGGED_IN}     },
    /* USER      */   {{FtpErr, FS_ERR, FS_ERR}, {CheckUser, FS_WAIT_FOR_PSWD1, FS_WAIT_FOR_USR},{FtpErr, FS_WAIT_FOR_PSWD1, FS_WAIT_FOR_PSWD1} , {FtpErr, FS_WAIT_FOR_PSWD2, FS_WAIT_FOR_PSWD2}, {FtpErr, FS_WAIT_FOR_PSWD3, FS_WAIT_FOR_PSWD3} , {FtpErr, FS_LOGGED_IN, FS_LOGGED_IN}     },
    /* PASSWORD    */ {{FtpErr, FS_ERR, FS_ERR}, {FtpErr, FS_WAIT_FOR_USR, FS_WAIT_FOR_USR}    , {CheckPswd, FS_LOGGED_IN, FS_WAIT_FOR_PSWD2}   , {CheckPswd, FS_LOGGED_IN, FS_WAIT_FOR_PSWD3}  , {CheckPswd, FS_LOGGED_IN, FS_WAIT_FOR_USR }    , {FtpErr, FS_LOGGED_IN, FS_LOGGED_IN}     },
    /* CD          */ {{FtpErr, FS_ERR, FS_ERR}, {FtpErr, FS_WAIT_FOR_USR, FS_WAIT_FOR_USR}    , {FtpErr, FS_WAIT_FOR_PSWD1, FS_WAIT_FOR_PSWD1} , {FtpErr, FS_WAIT_FOR_PSWD2, FS_WAIT_FOR_PSWD2}, {FtpErr, FS_WAIT_FOR_PSWD3, FS_WAIT_FOR_PSWD3} , {FtpCd, FS_LOGGED_IN, FS_LOGGED_IN }     },
    /* LS          */ {{FtpErr, FS_ERR, FS_ERR}, {FtpErr, FS_WAIT_FOR_USR, FS_WAIT_FOR_USR}    , {FtpErr, FS_WAIT_FOR_PSWD1, FS_WAIT_FOR_PSWD1} , {FtpErr, FS_WAIT_FOR_PSWD2, FS_WAIT_FOR_PSWD2}, {FtpErr, FS_WAIT_FOR_PSWD3, FS_WAIT_FOR_PSWD3} , {FtpLs, FS_LOGGED_IN, FS_LOGGED_IN }     },
    /* Bye         */ {{FtpErr, FS_ERR, FS_ERR}, {FtpErr, FS_WAIT_FOR_USR, FS_WAIT_FOR_USR}    , {FtpErr, FS_WAIT_FOR_PSWD1, FS_WAIT_FOR_PSWD1} , {FtpErr, FS_WAIT_FOR_PSWD2, FS_WAIT_FOR_PSWD2}, {FtpErr, FS_WAIT_FOR_PSWD3, FS_WAIT_FOR_PSWD3} , {FtpBye, FS_LOGGED_IN, FS_LOGGED_IN }    },
    /* Get         */ {{FtpErr, FS_ERR, FS_ERR}, {FtpErr, FS_WAIT_FOR_USR, FS_WAIT_FOR_USR}    , {FtpErr, FS_WAIT_FOR_PSWD1, FS_WAIT_FOR_PSWD1} , {FtpErr, FS_WAIT_FOR_PSWD2, FS_WAIT_FOR_PSWD2}, {FtpErr, FS_WAIT_FOR_PSWD3, FS_WAIT_FOR_PSWD3} , {FtpGet, FS_LOGGED_IN, FS_LOGGED_IN }    },
    /* Put         */ {{FtpErr, FS_ERR, FS_ERR}, {FtpErr, FS_WAIT_FOR_USR, FS_WAIT_FOR_USR}    , {FtpErr, FS_WAIT_FOR_PSWD1, FS_WAIT_FOR_PSWD1} , {FtpErr, FS_WAIT_FOR_PSWD2, FS_WAIT_FOR_PSWD2}, {FtpErr, FS_WAIT_FOR_PSWD3, FS_WAIT_FOR_PSWD3} , {FtpPut, FS_LOGGED_IN, FS_LOGGED_IN }    }
                          };

    assert(_fileName);

    fServer = (FtpServer*)malloc(sizeof(FtpServer));
    if(!fServer)
    {
        perror("error on allocating FTP server");
        return 0;
    }

    fServer->m_baseDir = get_current_dir_name();                 /*  Allocate and put the CWD                    */
    if(!fServer->m_baseDir)
    {
        perror("Base dir");
        goto clearBaseDir;
    }

    memcpy(fServer->m_eventTable, table, sizeof(table)) ;       /*  Copy the event table to FtpServer struct    */
    fServer->m_packetSize = _packetSize;

    fServer->m_ftpClientTree = TreeNew(GSKeyLessFunc);          /*  Create the client's tree    */
    if(!fServer->m_ftpClientTree)
    {
        fprintf(stderr, "error on creating tree\n");
        goto clearClientTree;
    }

    fServer->m_server = GenServerNew(_port, fServer, CMD_HEADER_SIZE, GetFtpHeader, NewFtpClient, NewFtpMssg, FtpDisconnect);   /*  Create Generic Server   */
    if(!fServer->m_server)
    {
        fprintf(stderr, "error on creating generic server\n");
        goto clearGenServer;
    }

    fServer->m_usrAndPswdTree = TreeNew(UsrLessFunc);           /*  Creating user and passwords tree            */
    if(!fServer->m_usrAndPswdTree)
    {
        fprintf(stderr, "error on creating tree\n");
        goto clearUsrTree;
    }

    if(InsertUsrAndPswd2Tree(fServer->m_usrAndPswdTree, _fileName))   /*  Fill the user and password tree according to User's input   */
    {
        goto clearUsrInsertTree;
    }

    return fServer;

    /*  Labels for goto */
    clearUsrInsertTree:
        TreeDel(fServer->m_usrAndPswdTree);
    clearUsrTree:
        GenServerDel(fServer->m_server);
    clearGenServer:
        TreeDel(fServer->m_ftpClientTree);
    clearClientTree:
        free(fServer->m_baseDir);
    clearBaseDir:
        free(fServer);
        return 0;
}


int FtpServerRun(FtpServer* _fServer)
{
    assert(_fServer);

    puts("FtpServer is ready for use\nWaiting for connections");

    return GenServerRun(_fServer->m_server);
}

















/************************************************************************************************************************************************************************/
/************************************************************************************************************************************************************************/
/************************************************************************************************************************************************************************/
#ifdef FTP_SERVER_UTEST

int ParserTest()
{
    char buffer[] = "ls";
    FtpEvent event;
    char resParam[30];

    if(FtpParser(buffer, strlen(buffer) + 1, &event, resParam))
    {
        fprintf(stderr, "error on parser\n");
        return 1;
    }

    printf("inserted buffer: %s , event number: %d , parameters: %s\n", buffer, event, resParam);

    return 0;
}

int Test()
{
    char* usrArr[] = {"dana", "sasi", "nimi","yosi"};
    char* pssArr[] = {"anad", "isas", "imin","isoy"};
    char usr[] = "user yosi";

    FtpServer* server = FtpServerNew(7, usrArr,pssArr,4,1000);
    if(!server)
    {
        fprintf(stderr, "error\n");
        return -1;
    }

    NewFtpClient(server, (GSKey)0xFF);
    NewFtpMssg(server, (GSKey)0xFF, usr, strlen(usr) );

    return 0;
}

int main(int argc, char** argv)
{
    char* usrArr[] = {"dana", "sasi", "nimi","yosi"};
    char* pssArr[] = {"anad", "isas", "imin","isoy"};
    FtpServer* server = 0;
    /*TreeIter it;*/

    /*Test();*/
    /*FtpServer* server = FtpServerNew(7, usrArr,pssArr,4,1000);
    if(!server)
    {
        fprintf(stderr, "error\n");
        return -1;
    }

    for(it = TreeBegin(server->m_usrAndPswdTree); !TreeIsEqual(it, TreeEnd(server->m_usrAndPswdTree)); it = TreeNext(it))
    {
        printf("The user is: %s , the psswd is: %s\n", ((UsrAndPswd*)TreeGetData(it))->m_usr, ((UsrAndPswd*)TreeGetData(it))->m_pswd);
    }*/

    /*ParserTest();*/
    if(argc < 3)
    {
        puts("Usage: FtpServer <FILE>  <PORT>  [opt: PACKET_SIZE]");
        return -1;
    }
    printf("%d\n", atoi(argv[2]));
    server = FtpServerNew(atoi(argv[2]), usrArr,pssArr,4,1000);
    if(!server)
    {
        fprintf(stderr, "error\n");
        return -1;
    }
    /*printf("size: %d\n",sizeof(FtpHeader));*/

    FtpServerRun(server);

/*    printf("%d\n",(server->m_eventTable[1][2]).nextState);*/

    printf("Hello world!\n");
    return 0;
}
#endif /*   FTP_SERVER_UTEST    */
